


<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    
    <meta charset="utf-8">
    <meta name="baidu-site-verification" content="5f1f7ad2a57c201714656f516cf11b94"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>

    <title>DHT Protocol：BitTorrent DHT 协议中文翻译 - justjavac(迷渡)</title>
    
    
    <meta name="author" content="justjavac">
    <link rel="alternate" title="justjavac(迷渡)" href="/atom.xml" type="application/rss+xml" />
  	<!--[if lt IE 9]>
  	<script src="http://libs.useso.com/js/html5shiv/3.7/html5shiv.min.js"></script>
  	<script src="http://libs.useso.com/js/respond.js/1.4.2/respond.min.js"></script>
  	<![endif]-->

    <link href="/assets/themes/twitter/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <link href="/assets/themes/twitter/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" />
    <link href="/assets/themes/twitter/css/style.css?v=201312251006" rel="stylesheet" media="all" />
    <link href="/assets/themes/twitter/css/pygments.css?v=201312251006" rel="stylesheet" media="all" />

    <link rel="index prerender" title="justjavac(迷渡)" href="/" />
    
    <link rel="prev prerender" href="/php/2015/01/11/strange-behaviour-php-incrementing-and-decrementing-operators.html" title="PHP 中「自增、自减」运算引发的奇怪问题" />
    
    
    <link rel="next prerender" href="/other/2015/02/01/magnet-and-torrent-search-engine-architecture-based-on-dht.html" title="基于 DHT 网络的磁力链接和BT种子的搜索引擎架构" />
    
    
    <link rel="apple-touch-icon" href="/assets/themes/twitter/images/apple-touch-icon.png">
    <link rel="apple-touch-icon" sizes="72x72" href="/assets/themes/twitter/images/apple-touch-icon-72x72.png">
    <link rel="apple-touch-icon" sizes="114x114" href="/assets/themes/twitter/images/apple-touch-icon-114x114.png">
    
  </head>

  <body>

    <div class="navbar navbar-fixed-top">
      <div class="navbar-inner">
        <div class="container">
          <a class="brand hidden-phone" href="/">justjavac(迷渡)</a>
          <ul class="nav">
            <li class="visible-phone"><a href="/">首页</a></li>
            <li><a href="/codepuzzle.html">代码之谜</a></li>
            <li><a href="/categories.html">目录</a></li>
            <li class="hidden-phone"><a href="/about.html">关于迷渡</a></li>
            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown">我在这里<b class="caret"></b></a>
              <ul class="dropdown-menu">
                <li><a href="http://weibo.com/justjavac" target="_blank" rel="noopener">微博</a></li>
                <li><a href="http://www.zhihu.com/people/justjavac.com" target="_blank" rel="noopener">知乎</a></li>
                <li><a href="http://segmentfault.com/u/justjavac" target="_blank" rel="noopener">SegmentFault</a></li>
                <li><a href="https://github.com/justjavac" target="_blank" rel="noopener">GitHub</a></li>
                <li><a href="http://stackoverflow.com/users/343194/justjavac" target="_blank" rel="noopener">StackOverFlow</a></li>
              </ul>
            </li>
          </ul>
        </div>
      </div>
    </div>

    <div class="container">
      <div class="content">
     

<div class="page-header">
  <h1>DHT Protocol：BitTorrent DHT 协议中文翻译 <small></small></h1>
</div>
<div class="row-fluid">
  <div class="span8">  
    <div id="article">
    <h2 id="前言">前言</h2>

<p>做了一个磁力链接和BT种子的搜索引擎 <a href="http://magnet-torrent.com">{Magnet &amp; Torrent}</a>，因此把 <a href="http://www.bittorrent.org/beps/bep_0005.html">DHT</a> 协议重新看了一遍。</p>

<blockquote>
  <p><strong>BEP</strong>: 5<br />
<strong>Title</strong>: DHT Protocol <br />
<strong>Version</strong>: 3dec52cb3ae103ce22358e3894b31cad47a6f22b<br />
<strong>Last-Modified</strong>: Tue Apr 2 16:51:45 2013 -0700<br />
<strong>Author</strong>: Andrew Loewenstern <a href="mailto:drue@bittorrent.com">drue@bittorrent.com</a>, Arvid Norberg <a href="mailto:arvid@bittorrent.com">arvid@bittorrent.com</a><br />
<strong>Status</strong>: Draft<br />
<strong>Type</strong>: Standards Track<br />
<strong>Created</strong>: 31-Jan-2008<br />
<strong>Post-History</strong>: 22-March-2013: Add “implied_port” to announce_peer message, to improve NAT support</p>
</blockquote>

<p>BitTorrent 使用”分布式哈希表”(DHT)来为无 tracker 的种子(torrents)存储 peer 之间的联系信息。这样每个 peer 都成了 tracker。这个协议基于 Kademila[1] 网络并且在 UDP 上实现。</p>

<p>请注意本文档中使用的术语，以免混乱。</p>

<ul>
  <li>“peer” 是在一个 TCP 端口上监听的客户端/服务器，它实现了 BitTorrent 协议。</li>
  <li>“节点” 是在一个 UDP 端口上监听的客户端/服务器，它实现了 DHT(分布式哈希表) 协议。</li>
</ul>

<p>DHT 由节点组成，它存储了 peer 的位置。BitTorrent 客户端包含一个 DHT 节点，这个节点用来联系 DHT 中其他节点，从而得到 peer 的位置，进而通过 BitTorrent 协议下载。</p>

<h2 id="概述-overview">概述 Overview</h2>

<p>每个节点有一个全局唯一的标识符，作为 “node ID”。节点 ID 是一个随机选择的 160bit 空间，BitTorrent infohash[2] 也使用这样的 160bit 空间。
“距离”用来比较两个节点 ID 之间或者节点 ID 和 infohash 之间的”远近”。节点必须维护一个路由表，路由表中含有一部分其它节点的联系信息。其它节点距离自己越近时，路由表信息越详细。因此每个节点都知道 DHT 中离自己很”近”的节点的联系信息，而离自己非常远的 ID 的联系信息却知道的很少。</p>

<p>在 Kademlia 网络中，距离是通过异或(XOR)计算的，结果为无符号整数。<code class="highlighter-rouge">distance(A, B) = |A xor B|</code>，值越小表示越近。</p>

<p>当节点要为 torrent 寻找 peer 时，它将自己路由表中的节点 ID 和 torrent 的 infohash 进行”距离对比”。然后向路由表中离 infohash 最近的节点发送请求，问它们正在下载这个 torrent 的 peer 的联系信息。如果一个被联系的节点知道下载这个 torrent 的 peer 信息，那个 peer 的联系信息将被回复给当前节点。否则，那个被联系的节点则必须回复在它的路由表中离该 torrent 的 infohash 最近的节点的联系信息。最初的节点重复地请求比目标 infohash 更近的节点，直到不能再找到更近的节点为止。查询完了之后，客户端把自己作为一个 peer 插入到所有回复节点中离种子最近的那个节点中。</p>

<p>请求 peer 的返回值包含一个不透明的值，称之为”令牌(token)”。如果一个节点宣布它所控制的 peer 正在下载一个种子，它必须在回复请求节点的同时，附加上对方向我们发送的最近的”令牌(token)”。这样当一个节点试图”宣布”正在下载一个种子时，被请求的节点核对令牌和发出请求的节点的 IP 地址。这是为了防止恶意的主机登记其它主机的种子。由于令牌仅仅由请求节点返回给收到令牌的同一个节点，所以没有规定他的具体实现。但是令牌必须在一个规定的时间内被接受，超时后令牌则失效。在 BitTorrent 的实现中，token 是在 IP 地址后面连接一个 secret(通常是一个随机数)，这个 secret 每五分钟改变一次，其中 token 在十分钟以内是可接受的。</p>

<h2 id="路由表-routing-table">路由表 Routing Table</h2>

<p>每个节点维护一个路由表保存已知的好节点。路由表中的节点是用来作为在 DHT 中请求的起始点。路由表中的节点是在不断的向其他节点请求过程中，对方节点回复的。</p>

<p>并不是我们在请求过程中收到得节点都是平等的，有的节点是好的，而另一些则不是。许多使用 DHT 协议的节点都可以发送请求并接收回复，但是不能主动回复其他节点的请求。节点的路由表只包含已知的好节点，这很重要。好节点是指在过去的 15 分钟以内，曾经对我们的某一个请求给出过回复的节点，或者曾经对我们的请求给出过一个回复(不用在15分钟以内)，并且在过去的 15 分钟给我们发送过请求。上述两种情况都可将节点视为好节点。在 15 分钟之后，对方没有上述 2 种情况发生，这个节点将变为可疑的。当节点不能给我们的一系列请求给出回复时，这个节点将变为坏的。相比那些未知状态的节点，已知的好节点会被给于更高的优先级。</p>

<p>路由表覆盖从 0 到 2^160 全部的节点 ID 空间。路由表又被划分为桶(bucket)，每个桶包含一部分的 ID 空间。空的路由表只有一个桶，它的 ID 范围从 min=0 到 max=2^160。当 ID 为 <code class="highlighter-rouge">N</code> 的节点插入到表中时，它将被放到 ID 范围在 <code class="highlighter-rouge">min &lt;= N &lt; max</code> 的 桶 中。空的路由表只有一个桶，所以所有的节点都将被放到这个桶中。每个桶最多只能保存 K 个节点，当前 K=8。当一个桶放满了好节点之后，将不再允许新的节点加入，除非我们自身的节点 ID 在这个桶的范围内。在这样的情况下，这个桶将被分裂为 2 个新的桶，每个新桶的范围都是原来旧桶的一半。原来旧桶中的节点将被重新分配到这两个新的桶中。如果一个新表只有一个桶，这个包含整个范围的桶将总被分裂为 2 个新的桶，每个桶的覆盖范围从 0..2^159 和 2^159..2^160。</p>

<p>当桶装满了好节点，新的节点会被丢弃。一旦桶中的某个节点变为了坏的节点，那么我们就用新的节点来替换这个坏的节点。如果桶中有在 15 分钟内都没有活跃过的节点，我们将这样的节点视为可疑的节点，这时我们向最久没有联系的节点发送 ping。如果被 ping 的节点给出了回复，那么我们向下一个可疑的节点发送 ping，不断这样循环下去，直到有某一个节点没有给出 ping 的回复，或者当前桶中的所有节点都是好的(也就是所有节点都不是可疑节点，他们在过去 15 分钟内都有活动)。如果桶中的某个节点没有对我们的 ping 给出回复，我们最好再试一次(再发送一次 ping，因为这个节点也许仍然是活跃的，但由于网络拥塞，所以发生了丢包现象，注意 DHT 的包都是 UDP 的)，而不是立即丢弃这个节点或者直接用新节点来替代它。这样，我们得路由表将充满稳定的长时间在线的节点。</p>

<p>每个桶都应该维持一个 <code class="highlighter-rouge">lastchange</code> 字段来表明桶中节点的”新鲜”度。当桶中的节点被 ping 并给出了回复，或者一个节点被加入到了桶，或者一个节点被新的节点所替代，桶的 <code class="highlighter-rouge">lastchange</code> 字段都应当被更新。如果一个桶的 <code class="highlighter-rouge">lastchange</code> 在过去的 15 分钟内都没有变化，那么我们将更新它。这个更新桶操作是这样完成的：从这个桶所覆盖的范围中随机选择一个 ID，并对这个 ID 执行 <code class="highlighter-rouge">find_nodes</code> 查找操作。常常收到请求的节点通常不需要常常更新自己的桶，反之，不常常收到请求的节点常常需要周期性的执行更新所有桶的操作，这样才能保证当我们用到 DHT 的时候，里面有足够多的好的节点。</p>

<p>在插入第一个节点到路由表并启动服务后，这个节点应试着查找 DHT 中离自己更近的节点，这个查找工作是通过不断的发出 <code class="highlighter-rouge">find_node</code> 消息给越来越近的节点来完成的，当不能找到更近的节点时，这个扩散工作就结束了。路由表应当被启动工作和客户端软件保存（也就是启动的时候从客户端中读取路由表信息，结束的时候客户端软件记录到文件中）。</p>

<h2 id="bittorrent-协议扩展-bittorrent-protocol-extension">BitTorrent 协议扩展 BitTorrent Protocol Extension</h2>

<p>BitTorrent 协议已经被扩展为可以在通过 tracker 得到的 peer 之间互相交换节点的 UDP 端口号(也就是告诉对方我们的 DHT 服务端口号)，在这样的方式下，客户端可以通过下载普通的种子文件来自动扩展 DHT 路由表。新安装的客户端第一次试着下载一个无 tracker 的种子时，它的路由表中将没有任何节点，这是它需要在 torrent 文件中找到联系信息。</p>

<p>peers 如果支持 DHT 协议就将 BitTorrent 协议握手消息的保留位的第 8 字节的最后一位置为 <code class="highlighter-rouge">1</code>。这时如果 peer 收到一个 <code class="highlighter-rouge">handshake</code> 表明对方支持 DHT 协议，就应该发送 PORT 消息。它由字节 <code class="highlighter-rouge">0x09</code> 开始，<code class="highlighter-rouge">payload</code> 的长度是 2 个字节，包含了这个 peer 的 DHT 服务使用的网络字节序的 UDP 端口号。当 peer 收到这样的消息是应当向对方的 IP 和消息中指定的端口号的节点发送 ping。如果收到了 ping 的回复，那么应当使用上述的方法将新节点的联系信息加入到路由表中。</p>

<h2 id="torrent-文件扩展-torrent-file-extensions">Torrent 文件扩展 Torrent File Extensions</h2>

<p>一个无 tracker 的 torrent 文件字典不包含 <code class="highlighter-rouge">announce</code> 关键字，而使用 <code class="highlighter-rouge">nodes</code> 关键字来替代。这个关键字对应的内容应该设置为 torrent 创建者的路由表中 K 个最接近的节点。可供选择的，这个关键字也可以设置为一个已知的可用节点，比如这个 torrent 文件的创建者。请不要自动加入 <code class="highlighter-rouge">router.bittorrent.com</code> 到 torrent 文件中或者自动加入这个节点到客户端路由表中。</p>

<ul>
  <li>nodes = <code class="highlighter-rouge">[["&lt;host&gt;", &lt;port&gt;], ["&lt;host&gt;", &lt;port&gt;], ...]</code></li>
  <li>nodes = <code class="highlighter-rouge">[["127.0.0.1", 6881], ["your.router.node", 4804]]</code></li>
</ul>

<h2 id="krpc-协议-krpc-protocol">KRPC 协议 KRPC Protocol</h2>

<p>KRPC 协议是由 bencode 编码组成的一个简单的 RPC 结构，他使用 UDP 报文发送。一个独立的请求包被发出去然后一个独立的包被回复。这个协议没有重发。它包含 3 种消息：请求，回复和错误。对DHT协议而言，这里有 4 种请求：<code class="highlighter-rouge">ping</code>，<code class="highlighter-rouge">find_node</code>，<code class="highlighter-rouge">get_peers</code> 和 <code class="highlighter-rouge">announce_peer</code>。</p>

<p>一条 KRPC 消息由一个独立的字典组成，其中有 2 个关键字是所有的消息都包含的，其余的附加关键字取决于消息类型。每条消息都包含 <code class="highlighter-rouge">t</code> 关键字，它是一个代表了 transaction ID 的字符串类型。transaction ID 由请求节点产生，并且回复中要包含回显该字段，所以回复可能对应一个节点的多个请求。transaction ID 应当被编码为一个短的二进制字符串，比如 2 个字节，这样就可以对应 2^16 个请求。另外每个 KRPC 消息还应该包含的关键字是 <code class="highlighter-rouge">y</code>，它由一个字节组成，表明这个消息的类型。<code class="highlighter-rouge">y</code> 对应的值有三种情况：<code class="highlighter-rouge">q</code> 表示请求，<code class="highlighter-rouge">r</code> 表示回复，<code class="highlighter-rouge">e</code> 表示错误。</p>

<h3 id="联系信息编码-contact-encoding">联系信息编码 Contact Encoding</h3>

<p>Peers 的联系信息被编码为 6 字节的字符串。又被称为 “CompactIP-address/port info”，其中前 4 个字节是网络字节序的 IP 地址，后 2 个字节是网络字节序的端口。</p>

<p>节点的联系信息被编码为 26 字节的字符串。又被称为 “Compactnode info”，其中前 20 字节是网络字节序的节点 ID，后面 6 个字节是 peers 的 “CompactIP-address/port info”。</p>

<h3 id="请求-queries">请求 Queries</h3>

<p>请求，对应于 KPRC 消息字典中的 <code class="highlighter-rouge">y</code> 关键字的值是 <code class="highlighter-rouge">q</code>，它包含 2 个附加的关键字 <code class="highlighter-rouge">q</code> 和 <code class="highlighter-rouge">a</code>。关键字 <code class="highlighter-rouge">q</code> 是字符串类型，包含了请求的方法名字。关键字 <code class="highlighter-rouge">a</code> 一个字典类型包含了请求所附加的参数。</p>

<h3 id="回复-responses">回复 Responses</h3>

<p>回复，对应于 KPRC 消息字典中的 <code class="highlighter-rouge">y</code> 关键字的值是 <code class="highlighter-rouge">r</code>，包含了一个附加的关键字 <code class="highlighter-rouge">r</code>。关键字 <code class="highlighter-rouge">r</code> 是字典类型，包含了返回的值。发送回复消息是在正确解析了请求消息的基础上完成的。</p>

<h3 id="错误-errors">错误 Errors</h3>

<p>错误，对应于 KPRC 消息字典中的 <code class="highlighter-rouge">y</code> 关键字的值是 <code class="highlighter-rouge">e</code>，包含一个附加的关键字 <code class="highlighter-rouge">e</code>。关键字 <code class="highlighter-rouge">e</code> 是列表类型。第一个元素是数字类型，表明了错误码。第二个元素是字符串类型，表明了错误信息。当一个请求不能解析或出错时，错误包将被发送。下表描述了可能出现的错误码：</p>

<table class="table">
<tbody>
<tr>
<th>错误码</th>
<th>描述</th>
</tr>
<tr>
<td>201</td>
<td>一般错误</td>
</tr>
<tr>
<td>202</td>
<td>服务错误</td>
</tr>
<tr>
<td>203</td>
<td>协议错误，比如不规范的包，无效的参数，或者错误的 token</td>
</tr>
<tr>
<td>204</td>
<td>未知方法</td>
</tr>
</tbody>
</table>

<p><strong>错误包例子 Example Error Packets:</strong></p>

<ul>
  <li>generic error = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"e"</span><span class="p">,</span><span class="w"> </span><span class="nt">"e"</span><span class="p">:[</span><span class="mi">201</span><span class="p">,</span><span class="w"> </span><span class="s2">"A Generic Error Ocurred"</span><span class="p">]}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:y1:ee</code></li>
</ul>

<h2 id="dht-请求-dht-queries">DHT 请求 DHT Queries</h2>

<p>所有的请求都包含一个关键字 <code class="highlighter-rouge">id</code>，它包含了请求节点的节点 ID。所有的回复也包含关键字<code class="highlighter-rouge"> id</code>，它包含了回复节点的节点 ID。</p>

<h3 id="ping">ping</h3>

<p>最基础的请求就是 ping。这时 KPRC 协议中的 <code class="highlighter-rouge">"q" = "ping"</code>。Ping 请求包含一个参数 <code class="highlighter-rouge">id</code>，它是一个 20 字节的字符串包含了发送者网络字节序的节点 ID。对应的 ping 回复也包含一个参数 <code class="highlighter-rouge">id</code>，包含了回复者的节点 ID。</p>

<ul>
  <li>参数: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;querying nodes id&gt;"</span><span class="p">}</span></code></li>
  <li>回复: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;queried nodes id&gt;"</span><span class="p">}</span></code></li>
</ul>

<p><strong>报文包例子 Example Packets</strong></p>

<ul>
  <li>ping Query = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"q"</span><span class="p">,</span><span class="w"> </span><span class="nt">"q"</span><span class="p">:</span><span class="s2">"ping"</span><span class="p">,</span><span class="w"> </span><span class="nt">"a"</span><span class="p">:{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"abcdefghij0123456789"</span><span class="p">}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe</code></li>
  <li>Response = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"r"</span><span class="p">,</span><span class="w"> </span><span class="nt">"r"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"mnopqrstuvwxyz123456"</span><span class="p">}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re</code></li>
</ul>

<h3 id="find_node">find_node</h3>

<p><code class="highlighter-rouge">find_node</code> 被用来查找给定 ID 的节点的联系信息。这时 KPRC 协议中的 <code class="highlighter-rouge">"q" == "find_node"</code>。<code class="highlighter-rouge">find_node</code> 请求包含 2 个参数，第一个参数是 <code class="highlighter-rouge">id</code>，包含了请求节点的ID。第二个参数是 <code class="highlighter-rouge">target</code>，包含了请求者正在查找的节点的 ID。当一个节点接收到了 <code class="highlighter-rouge">find_node</code> 的请求，他应该给出对应的回复，回复中包含 2 个关键字 <code class="highlighter-rouge">id</code> 和 <code class="highlighter-rouge">nodes</code>，<code class="highlighter-rouge">nodes</code> 是字符串类型，包含了被请求节点的路由表中最接近目标节点的 K(8) 个最接近的节点的联系信息。</p>

<ul>
  <li>参数: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;querying nodes id&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nt">"target"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;id of target node&gt;"</span><span class="p">}</span></code></li>
  <li>回复: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;queried nodes id&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nt">"nodes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;compact node info&gt;"</span><span class="p">}</span></code></li>
</ul>

<p><strong>报文包例子 Example Packets</strong></p>

<ul>
  <li>find_node Query = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"q"</span><span class="p">,</span><span class="w"> </span><span class="nt">"q"</span><span class="p">:</span><span class="s2">"find_node"</span><span class="p">,</span><span class="w"> </span><span class="nt">"a"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"abcdefghij0123456789"</span><span class="p">,</span><span class="w"> </span><span class="nt">"target"</span><span class="p">:</span><span class="s2">"mnopqrstuvwxyz123456"</span><span class="p">}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe</code></li>
  <li>Response = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"r"</span><span class="p">,</span><span class="w"> </span><span class="nt">"r"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"0123456789abcdefghij"</span><span class="p">,</span><span class="w"> </span><span class="nt">"nodes"</span><span class="p">:</span><span class="w"> </span><span class="s2">"def456..."</span><span class="p">}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:re</code></li>
</ul>

<h3 id="get_peers">get_peers</h3>

<p><code class="highlighter-rouge">get_peers</code> 与 torrent 文件的 infohash 有关。这时 KPRC 协议中的 <code class="highlighter-rouge">"q" = "get_peers"</code>。<code class="highlighter-rouge">get_peers</code> 请求包含 2 个参数。第一个参数是 <code class="highlighter-rouge">id</code>，包含了请求节点的 ID。第二个参数是 <code class="highlighter-rouge">info_hash</code>，它代表 torrent 文件的 infohash。如果被请求的节点有对应 <code class="highlighter-rouge">info_hash</code> 的 peers，他将返回一个关键字 <code class="highlighter-rouge">values</code>，这是一个列表类型的字符串。每一个字符串包含了 <code class="highlighter-rouge">"CompactIP-address/portinfo"</code> 格式的 peers 信息。如果被请求的节点没有这个 infohash 的 peers，那么他将返回关键字 <code class="highlighter-rouge">nodes</code>，这个关键字包含了被请求节点的路由表中离 <code class="highlighter-rouge">info_hash</code> 最近的 K 个节点，使用 <code class="highlighter-rouge">"Compactnodeinfo"</code> 格式回复。在这两种情况下，关键字 <code class="highlighter-rouge">token</code> 都将被返回。<code class="highlighter-rouge">token</code> 关键字在今后的 <code class="highlighter-rouge">annouce_peer</code> 请求中必须要携带。<code class="highlighter-rouge">token</code> 是一个短的二进制字符串。</p>

<ul>
  <li>参数: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;querying nodes id&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nt">"info_hash"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;20-byte infohash of target torrent&gt;"</span><span class="p">}</span></code></li>
  <li>回复: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;queried nodes id&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nt">"token"</span><span class="w"> </span><span class="p">:</span><span class="s2">"&lt;opaque write token&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nt">"values"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"&lt;peer 1 info string&gt;"</span><span class="p">,</span><span class="w"> </span><span class="s2">"&lt;peer 2 info string&gt;"</span><span class="p">]}</span></code></li>
  <li>或: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;queried nodes id&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nt">"token"</span><span class="w"> </span><span class="p">:</span><span class="s2">"&lt;opaque write token&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nt">"nodes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;compact node info&gt;"</span><span class="p">}</span></code></li>
</ul>

<p><strong>报文包例子 Example Packets:</strong></p>

<ul>
  <li>get_peers Query = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"q"</span><span class="p">,</span><span class="w"> </span><span class="nt">"q"</span><span class="p">:</span><span class="s2">"get_peers"</span><span class="p">,</span><span class="w"> </span><span class="nt">"a"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"abcdefghij0123456789"</span><span class="p">,</span><span class="w"> </span><span class="nt">"info_hash"</span><span class="p">:</span><span class="s2">"mnopqrstuvwxyz123456"</span><span class="p">}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe</code></li>
  <li>Response with peers = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"r"</span><span class="p">,</span><span class="w"> </span><span class="nt">"r"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"abcdefghij0123456789"</span><span class="p">,</span><span class="w"> </span><span class="nt">"token"</span><span class="p">:</span><span class="s2">"aoeusnth"</span><span class="p">,</span><span class="w"> </span><span class="nt">"values"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"axje.u"</span><span class="p">,</span><span class="w"> </span><span class="s2">"idhtnm"</span><span class="p">]}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re</code></li>
  <li>Response with closest nodes = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"r"</span><span class="p">,</span><span class="w"> </span><span class="nt">"r"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"abcdefghij0123456789"</span><span class="p">,</span><span class="w"> </span><span class="nt">"token"</span><span class="p">:</span><span class="s2">"aoeusnth"</span><span class="p">,</span><span class="w"> </span><span class="nt">"nodes"</span><span class="p">:</span><span class="w"> </span><span class="s2">"def456..."</span><span class="p">}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:rd2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe1:t2:aa1:y1:re</code></li>
</ul>

<h3 id="announce_peer">announce_peer</h3>

<p>这个请求用来表明发出 <code class="highlighter-rouge">announce_peer</code> 请求的节点，正在某个端口下载 torrent 文件。<code class="highlighter-rouge">announce_peer</code> 包含 4 个参数。第一个参数是 <code class="highlighter-rouge">id</code>，包含了请求节点的 ID；第二个参数是 <code class="highlighter-rouge">info_hash</code>，包含了 torrent 文件的 infohash；第三个参数是 <code class="highlighter-rouge">port</code> 包含了整型的端口号，表明 peer 在哪个端口下载；第四个参数数是 <code class="highlighter-rouge">token</code>，这是在之前的 <code class="highlighter-rouge">get_peers</code> 请求中收到的回复中包含的。收到 <code class="highlighter-rouge">announce_peer</code> 请求的节点必须检查这个 <code class="highlighter-rouge">token</code> 与之前我们回复给这个节点 <code class="highlighter-rouge">get_peers</code> 的 <code class="highlighter-rouge">token</code> 是否相同。如果相同，那么被请求的节点将记录发送 <code class="highlighter-rouge">announce_peer</code> 节点的 IP 和请求中包含的 port 端口号在 peer 联系信息中对应的 infohash 下。</p>

<ul>
  <li>参数: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;querying nodes id&gt;"</span><span class="p">,</span><span class="w">  </span><span class="nt">"implied_port"</span><span class="p">:</span><span class="w"> </span><span class="err">&lt;</span><span class="mi">0</span><span class="w"> </span><span class="err">or</span><span class="w"> </span><span class="mi">1</span><span class="err">&gt;</span><span class="p">,</span><span class="w"> </span><span class="nt">"info_hash"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;20-byte infohash of target torrent&gt;"</span><span class="p">,</span><span class="w">  </span><span class="nt">"port"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="err">&lt;port</span><span class="w"> </span><span class="err">number&gt;</span><span class="p">,</span><span class="w">  </span><span class="nt">"token"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;opaque token&gt;"</span><span class="p">}</span></code></li>
  <li>回复: <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;queried nodes id&gt;"</span><span class="p">}</span></code></li>
</ul>

<p><strong>报文包例子 Example Packets:</strong></p>

<ul>
  <li>announce_peers Query = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"q"</span><span class="p">,</span><span class="w"> </span><span class="nt">"q"</span><span class="p">:</span><span class="s2">"announce_peer"</span><span class="p">,</span><span class="w"> </span><span class="nt">"a"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"abcdefghij0123456789"</span><span class="p">,</span><span class="w"> </span><span class="nt">"implied_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nt">"info_hash"</span><span class="p">:</span><span class="s2">"mnopqrstuvwxyz123456"</span><span class="p">,</span><span class="w"> </span><span class="nt">"port"</span><span class="p">:</span><span class="w"> </span><span class="mi">6881</span><span class="p">,</span><span class="w"> </span><span class="nt">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aoeusnth"</span><span class="p">}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:ad2:id20:abcdefghij01234567899:info_hash20:&lt;br /&gt;
mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qe</code></li>
  <li>Response = <code class="highlighter-rouge"><span class="p">{</span><span class="nt">"t"</span><span class="p">:</span><span class="s2">"aa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="s2">"r"</span><span class="p">,</span><span class="w"> </span><span class="nt">"r"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"id"</span><span class="p">:</span><span class="s2">"mnopqrstuvwxyz123456"</span><span class="p">}}</span></code></li>
  <li>bencoded = <code class="highlighter-rouge">d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re</code></li>
</ul>

<h2 id="references">References</h2>

<ul>
  <li>[1] Peter Maymounkov, David Mazieres, “Kademlia: A Peer-to-peer Information System Based on the XOR Metric”, IPTPS 2002. <a href="http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf">http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf</a></li>
  <li>[2] Use SHA1 and plenty of entropy to ensure a unique ID.</li>
</ul>

<h2 id="copyright">Copyright</h2>

<p>This document has been placed in the public domain.</p>

    <p>继续阅读关于
    
    
    
       
      	<a href="/tags.html#Protocol-ref" rel="tag">Protocol</a>
       
      	<a href="/tags.html#DHT-ref" rel="tag">DHT</a>
       
      	<a href="/tags.html#BitTorrent-ref" rel="tag">BitTorrent</a>
      
    
    
    
    的文章</p>
    </div>  

    <hr/>

    <div class="pagination">
      <ul>
      
        <li class="prev"><a href="/php/2015/01/11/strange-behaviour-php-incrementing-and-decrementing-operators.html" title="PHP 中「自增、自减」运算引发的奇怪问题">&larr; 上一篇</a></li>
      
        <li><a href="/archive.html">归档</a></li>
      
        <li class="next"><a href="/other/2015/02/01/magnet-and-torrent-search-engine-architecture-based-on-dht.html" title="基于 DHT 网络的磁力链接和BT种子的搜索引擎架构">下一篇 &rarr;</a></li>
      
      </ul>
    </div>
    
    <hr>
    <!-- Duoshuo Comment BEGIN -->
    <div id="comments" class="ds-thread" data-title="DHT Protocol：BitTorrent DHT 协议中文翻译" data-url="http://justjavac.com/other/2015/02/01/bittorrent-dht-protocol.html" data-thread-key="/other/2015/02/01/bittorrent-dht-protocol"></div>
<script type="text/javascript">
var duoshuoQuery = {short_name:"justjavac"};
    (function() {
        var ds = document.createElement('script');
        ds.type = 'text/javascript';ds.async = true;
        ds.src = 'http://static.duoshuo.com/embed.js';
        ds.charset = 'UTF-8';
        (document.getElementsByTagName('head')[0] 
        || document.getElementsByTagName('body')[0]).appendChild(ds);
    })();
    </script>
<!-- Duoshuo Comment END -->
  </div>
  
  <aside class="span4">

  <section>
    <h3>公众号</h3>
    <p>
      欢迎订阅我的微信公众帐号，只推送原创文字。欢迎扫描二维码订阅：<br/>
      <img src="/assets/images/weixin-justjavac.jpg" alt="justjavac微信公众帐号"/>
    </p>
   </section>
    
    <section>
    <h3>推荐阅读</h3>
    <ul id="recent_posts">
      <li><a href="http://phpbestpractices.justjavac.com/" target="_blank" rel="noopener">PHP 最佳实践（译）</a></li>
      <li><a href="http://justjavac.com/php/2015/09/28/read-the-flarum-story-why-im-building-flarum.html" target="_blank" rel="noopener">Flarum 的故事：我为什么开发 Flarum</a></li>
      <li><a href="http://justjavac.com/named-function-expressions-demystified.html" target="_blank" rel="noopener">命名函数表达式探秘</a></li>
      <li><a href="http://justjavac.com/codepuzzle/2012/09/25/codepuzzle-introduction.html" target="_blank" rel="noopener">代码之谜 - 开篇/前言/序</a></li>
      <li><a href="http://justjavac.com/other/2012/05/15/qualified-programmer-should-read-what-books.html" target="_blank" rel="noopener">一个合格的程序员应该读过哪些书</a></li>
      <li><a href="http://justjavac.com/javascript/2012/05/24/can-you-explain-why-10.html" target="_blank" rel="noopener">为什么 ++[[]][+[]]+[+[]] = 10？</a></li>
      <li><a href="http://justjavac.com/other/2012/05/15/whats-wrong-with-the-for-loop.html" target="_blank" rel="noopener">for 循环为何可恨？</a></li>
      <li><a href="http://justjavac.com/javascript/2012/04/05/how-to-avoid-the-assignment-statement.html" target="_blank" rel="noopener">如何避免产生赋值语句</a></li>
      <li><a href="http://justjavac.com/javascript/2015/12/15/introducing-lazy-evaluation.html" target="_blank" rel="noopener">如何百倍加速 Lo-Dash？引入惰性计算</a></li>
    </ul>
    </section>
    
    <link rel="prerender" href="http://justjavac.com/codepuzzle/2012/09/25/codepuzzle-introduction.html">
    <link rel="prerender" href="http://justjavac.com/codepuzzle/2012/11/02/codepuzzle-float-from-surprised-to-ponder.html">
    <link rel="prerender" href="http://justjavac.com/javascript/2012/05/24/can-you-explain-why-10.html">
    <link rel="prerender" href="http://justjavac.com/codepuzzle/2012/10/28/codepuzzle-operator.html">
    <link rel="prerender" href="http://justjavac.com/other/2012/11/12/do-not-bool-as-function-parameters.html">
    <link rel="prerender" href="http://justjavac.com/other/2012/05/15/qualified-programmer-should-read-what-books.html">
    
    <section data-spy="affix" data-offset-top="418" class="hot_articles affix-top">
    <h3>热门文章</h3>
    <ul class="hots gray">
    <li class="textItem">
            <p class="itemTitle">
                <span class="hot">1</span>
                <a href="http://justjavac.com/codepuzzle/2012/09/25/codepuzzle-introduction.html" target="_blank" title="代码之谜（零）- 开篇/前言/序">
    代码之谜（零）- 开篇/前言/序</a></p>
            </li>
    <li class="textItem">
            <p class="itemTitle">
                <span class="hot">2</span>
                <a href="http://justjavac.com/codepuzzle/2012/11/02/codepuzzle-float-from-surprised-to-ponder.html" target="_blank" title="代码之谜（四）- 浮点数（从惊讶到思考）">
    代码之谜（四）- 浮点数（从惊讶到思考）</a></p>
            </li>
    <li class="textItem">
            <p class="itemTitle">
                <span class="hot">3</span>
                <a href="http://justjavac.com/javascript/2012/05/24/can-you-explain-why-10.html" target="_blank" title="为什么 ++[[]][+[]]+[+[]] = 10？">
    为什么 ++[[]][+[]]+[+[]] = 10？</a></p>
            </li>
    <li class="textItem">
            <p class="itemTitle">
                <span class="">4</span>
                <a href="http://justjavac.com/codepuzzle/2012/10/28/codepuzzle-operator.html" target="_blank" title="代码之谜（三）- 运算符">
    代码之谜（三）- 运算符</a></p>
            </li>
    <li class="textItem">
            <p class="itemTitle">
                <span class="">5</span>
                <a href="http://justjavac.com/other/2012/11/12/do-not-bool-as-function-parameters.html" target="_blank" title="千万不要把 bool 当成函数参数">
    千万不要把 bool 当成函数参数</a></p>
            </li>
    <li class="textItem">
            <p class="itemTitle">
                <span class="">6</span>
                <a href="http://justjavac.com/other/2012/05/15/qualified-programmer-should-read-what-books.html" target="_blank" title="一个合格的程序员应该读过哪些书">
    一个合格的程序员应该读过哪些书</a></p>
            </li>
    <li class="textItem">
            <p class="itemTitle">
                <span class="">7</span>
                <a href="http://justjavac.com/codepuzzle/2012/10/28/codepuzzle-expression-and-statement.html" target="_blank" title="代码之谜（二）- 语句与表达式">
    代码之谜（二）- 语句与表达式</a></p>
            </li>
    <li class="textItem">
            <p class="itemTitle">
                <span class="">8</span>
                <a href="http://justjavac.com/codepuzzle/2012/10/15/codepuzzle-finite-and-infinite.html" target="_blank" title="代码之谜（一）- 有限与无限">
    代码之谜（一）- 有限与无限</a></p>
            </li>
    <li class="textItem">
            <p class="itemTitle">
                <span class="">10</span>
                <a href="http://justjavac.com/codepuzzle/2012/11/11/codepuzzle-float-who-stole-your-accuracy.html" target="_blank" title="代码之谜（五）- 浮点数（谁偷了你的精度？）">
    代码之谜（五）- 浮点数（谁偷了你的精度？）</a></p>
            </li>
    </ul>
    </section>    

  </aside>
</div>


      </div>

      <footer>
        <p>&copy; <a href="http://weibo.com/justjavac" rel="noopener" target="_blank">@justjavac</a> 2012 
          基于 <a href="http://jekyllbootstrap.com" target="_blank" title="The Definitive Jekyll Blogging Framework" rel="nofollow noopener">Jekyll Bootstrap</a>
          和 <a href="http://twitter.github.com/bootstrap/" target="_blank" rel="nofollow noopener">Twitter Bootstrap</a> 构建, 
          源码托管在 <a href="https://github.com/justjavac/justjavac.github.com" target="_blank" rel="noopener">github</a> 上
        </p>
      </footer>

    </div>

    <a class="v-fork-me" href="https://github.com/justjavac/justjavac.github.com" target="_blank" rel="noopener">
      <img src="/assets/themes/twitter/bootstrap/img/forkme_right_darkblue.png" alt="Fork me on GitHub">
    </a>

    <script src="http://libs.baidu.com/jquery/1.9.0/jquery.min.js"></script>
    <script src="http://libs.baidu.com/jqueryui/1.9.0/jquery-ui.min.js "></script>
    <script src="http://libs.baidu.com/bootstrap/2.3.2/js/bootstrap.min.js"></script>
    <script src="/assets/js/bootstrap-affix.js"></script>

    <script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "//hm.baidu.com/hm.js?5578a27c6c70bf44e7509eda26137f4c";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
    </script>
       
  </body>
</html>

